package testings;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import weka.core.DistanceFunction;
import weka.core.EuclideanDistance;
import weka.core.Instance;
import weka.core.Instances;
import weka.filters.Filter;
import weka.filters.unsupervised.attribute.PrincipalComponents;
import Kmeans.mSimpleKmeans;
import NormalityTest.ADNormality;

/**
 * Input a dataset, to find best K by learning
 * each cluster is Normal distributed.
 * @author Ray
 *
 */
public class Gmeans {
	private int numberCluesters;
	private Instances orignal;
	private mSimpleKmeans sk;
	private double distances[][];
	private double smallestVaules[];
	private final int MAXCLUSTERS=100;
	private boolean nonstop=true;
	private List<Instances> subdatasets;
	private int minUB;
	public Gmeans(Instances orignal){
		this.numberCluesters=1;
		this.orignal=new Instances(orignal);
		this.sk=new mSimpleKmeans();
		
	}
	/**
	 * @return the minimum size of subset of data
	 */
	public int getMinUB(){
		minUB=Integer.MAX_VALUE;

	if(!subdatasets.isEmpty()){
			for(Instances x:subdatasets){
				
				if(x.size()<minUB){
					minUB=x.size();
				}
			}
		}
		return minUB;
	}
	/**
	 * @return number of clusters, each sub-datset of cluster is Normal distributed.
	 * @throws Exception
	 */
	public int doGmeans() throws Exception{
		
		while(nonstop){
			System.out.println("numberCluseters grows to "+numberCluesters);
			sk.setNumClusters(numberCluesters);
			sk.setInitializeUsingKMeansPlusPlusMethod(true);//using kmeans++
			sk.buildClusterer(orignal);
			
			createDistancesTable(orignal);
			smallestValTable(orignal);
			extratSubdatasets(orignal);
			
			int temp=numberCluesters;
			for(int i=0;i<subdatasets.size();++i){
				if(subdatasets.get(i).numInstances()>1){
					if((testNormal(subdatasets.get(i))<0.005)){						
						numberCluesters++;
					}
				}
			}
//			if(temp==numberCluesters||temp>MAXCLUSTERS){
//				nonstop=false;
//			}
			if(temp==numberCluesters){
				nonstop=false;
			}
		}		
		return numberCluesters;
	}
	
	/**
	 * @param dataset
	 */
	private void extratSubdatasets(Instances dataset){
		
		subdatasets=new ArrayList<Instances>();			
		for(int i=0;i<sk.getNumClusters();++i){
			Instances newdatasets=new Instances(dataset);
			newdatasets.clear();
			for(int j=0;j<dataset.size();++j){
				if(distances[i][j]==smallestVaules[j]){
					newdatasets.add(dataset.get(j));
				}				
			}
			if(!newdatasets.isEmpty()){
				subdatasets.add(newdatasets);
			}
		}
	}
	/**
	 * @param dataset
	 */
	private void smallestValTable(Instances dataset){
		smallestVaules=new double[dataset.size()];
		
		for(int j=0;j<dataset.size();++j){
			double smallest=Integer.MAX_VALUE;
			for(int i=0;i<sk.getNumClusters();++i){
				if(distances[i][j]<smallest){
					smallest=distances[i][j];
				}
			}
			smallestVaules[j]=smallest;
		}
	}
	/**
	 * @param dataset
	 * @throws Exception
	 */
	private void createDistancesTable(Instances dataset) throws Exception{
		sk.setNumClusters(numberCluesters);
		distances=new double[sk.getNumClusters()][dataset.size()];
		
		for(int i=0; i<sk.getNumClusters();++i){			
			for(int j=0; j<dataset.size();++j){
			//get sub-detest for each number clusters
			distances[i][j]=distanceBetween(j, sk.getClusterCentroids().get(i), dataset);
			}			
		}
	}

	/**
	 * @param toPca the dataset need to do PCA
	 * @param remains number of attribute want to keep
	 * @return new dataset that have done PCA
	 */
	private  Instances doPCA(Instances toPca,int remains){
		Instances results=null;
		
		PrincipalComponents p = new PrincipalComponents();
		
		try {
			p.setInputFormat(toPca);
			p.setMaximumAttributes(remains);
			//System.out.println("num att=:"+toPca.numAttributes());
			//System.out.println("num ins=:"+toPca.numInstances());
			results=Filter.useFilter(toPca, p);
			
			//System.out.println(newData.get(0));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return results;
		
	}
	/**
	 * @param toTest
	 * @return p-Values of AD
	 */
	private double testNormal(Instances toTest){
		double result=Integer.MIN_VALUE;
		
		Instances pcatoTest=new Instances(doPCA(toTest, 1));
		//System.out.println(pcatoTest.toString());
		double[] sampledate=new double[pcatoTest.size()];		

		for(int i=0;i<pcatoTest.size();++i){
			sampledate[i]=Double.parseDouble(pcatoTest.get(i).toString());
		}
		Arrays.sort(sampledate);
		ADNormality adn=new ADNormality(sampledate);
		
		result=adn.doAD();
		
		return result;
	}
	private double distanceBetween(int fromIndex, Instance toInstance, Instances inDataset){
		double result=0;		
		DistanceFunction m_D = new EuclideanDistance(inDataset);
		result=m_D.distance(inDataset.instance(fromIndex), toInstance);
		return result;
	}
}

